package helper.scxml.scxml2.zone.t2

import helper.base.A2LHM
import helper.base.BaseTypeHelper.LHMGetExpand.getMinKey
import helper.base.BaseTypeHelper.ListExpand.getMinValueKey
import helper.base.BaseTypeHelper.ListExpand.retainPercentage
import helper.base.BaseTypeHelper.ListExpand.toArrayList
import helper.base.BaseTypeHelper.isInt
import helper.base.FileHelper.Expand.toCreatedFile
import helper.base.LHMHelper.A2LHMExpand.add
import helper.base.LHMHelper.A2LHMExpand.toEDoubleLHM
import helper.base.StringHelper.removeEmptyLine
import helper.base.TimeHelper.getT4NowTimeString
import helper.base.ZoneConstraintHelper.CoeffVar.Companion.makeCoeffVar
import helper.base.ZoneConstraintHelper.ZoneConstraint.Companion.makePointZoneConstraint
import helper.base.debug.DebugHelper.DebuggerList
import helper.base.debug.DebugHelper.DebuggerList.Companion.makeDebuggerList
import helper.base.file.FromFileHelper.fileNameToDoubleList
import helper.base.file.FromFileHelper.fileNameToList
import helper.base.file.FromFileHelper.readDoubleListFromFile
import helper.base.list.DoubleList
import helper.base.list.ListToHelper.toHistoryMinList
import helper.base.math.DoubleMatrixHelper.DoubleMatrix
import helper.base.string.ReplaceHelper.replaceLastOccurrence
import helper.base.string.ToLStringHelper.IToL2String
import helper.base.string.ToStrHelper.IToStr
import helper.base.string.ToStrHelper.TabStringUnit.Companion.makeTabStringUnit
import helper.base.string.ToStrHelper.toSBStr
import helper.chart.ChartHelper.LineChartHelper.drawLineAndHistoryMinLineChart
import helper.chart.ChartHelper.LineChartHelper.drawLineChart
import helper.chart.ChartHelper.LineChartHelper.drawLinesChart
import helper.scxml.scxml2.EnvHelper.Expand.toLocationEventVListLHM
import helper.scxml.scxml2.EnvHelper.RunResult
import helper.scxml.scxml2.MathHelper.DSSUnit
import helper.scxml.scxml2.MathHelper.LocationEventVListLHM
import helper.scxml.scxml2.zone.t2.ConfigHelper.Config
import helper.scxml.scxml2.zone.t2.ConfigHelper.Config.Companion.makeConfig
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.IStrategyEx
import helper.scxml.scxml2.zone.t2.ZoneEnvHelper.ZoneEnv
import helper.scxml.scxml2.zone.t2.ZoneLearningHelper.InstanceArgUnit.Companion.makeInstanceArgUnit
import helper.scxml.strategy.TotalStrategyHelper.EDoubleLHM
import helper.scxml.strategy.TotalStrategyHelper.EDoubleLHM.Companion.makeEDoubleLHM
import helper.scxml.strategy.TotalStrategyHelper.SNode
import helper.scxml.strategy.TotalStrategyHelper.SNode.Companion.makeSNode
import helper.scxml.strategy.sNodeUnit.SNodeUnitHelper.ISNodeUnit
import helper.scxml.strategy.sNodeUnit.T2SNodeUnitHelper.SubT2SNodeUnit
import res.FileRes
import kotlin.properties.Delegates

object ZoneLearningHelper {
    interface IDsDFun {
        fun aDsToString(vararg ds: Double): Double
    }

    class GetDFun(
        val name: String,
        val getDFun: DSSUnit.(DoubleList) -> Double,
    ) {
        companion object {
            fun makeGetDFun(name: String, getDFun: DSSUnit.(DoubleList) -> Double): GetDFun {
                return GetDFun(name, getDFun)
            }

            val aGetDistanceToMean = makeGetDFun("d1", DSSUnit::getDistanceToMean)
            val aGetDistanceToCovarianceMatrix = makeGetDFun("d2", DSSUnit::getDistanceToCovarianceMatrix)
        }
    }

    class InstanceArgUnit(
        var countOfNoBetter: Int = 0,
        var countOfReset: Int = -1,
        var aRunResultList: ArrayList<RunResult> = ArrayList(),
        var lastMinCost: Double = Double.MAX_VALUE,
        var indexAfterReset: Int = 0,
    ) {
        var ifReset: Boolean = true

        companion object {
            fun makeInstanceArgUnit() = InstanceArgUnit()
        }
    }

    interface IGetTotalScore {
        fun getTotalScore(cal: A2LHM<String, Double>): Double

        fun getCalString(cal: A2LHM<String, Double>): String {
            return buildString {
                cal.forEach { (t, u) ->
                    append("${t}=")
                    append("$u".padEnd(25))
                }
                append("total=")
                append(getTotalScore(cal))
            }
        }

        fun plnCal(cal: A2LHM<String, Double>) {
            println(getCalString(cal))
        }
    }

    interface ISaveTuple

    class T1SaveTuple(
        val iOfAll: Int,
        val iOfReset: Int,
        val iAfterReset: Int,
        val totalScore: Double,
    ) : ISaveTuple {
        companion object {
            fun makeT1SaveTuple(
                iOfAll: Int,
                iOfReset: Int,
                iAfterReset: Int,
                totalScore: Double,
            ): T1SaveTuple {
                return T1SaveTuple(iOfAll, iOfReset, iAfterReset, totalScore)
            }

            fun parseFile(fileName: String): List<T1SaveTuple> {
                return fileName.fileNameToList { values ->
                    if (values.size == 4) {
                        makeT1SaveTuple(
                            values[0].toInt(),
                            values[1].toInt(),
                            values[2].toInt(),
                            values[3].toDouble(),
                        )
                    } else {
                        null
                    }
                }
            }

            fun parseFileToScoreList(fileName: String): List<Double> {
                return parseFile(fileName).map {
                    it.totalScore
                }
            }
        }
    }

    abstract class LearningHelper : IToStr, IGetTotalScore, IToL2String {
        var maxRuns_repeatNum: Int = 0
        var maxGood_repeatNum: Int = 0
        var maxBest_heapSize: Int = 0
        var maxNoBetter: Int = 0
        var maxIterations: Int = 0
        var maxResets: Int = 0
        var getDFun: GetDFun by Delegates.notNull()
        var clearHeapP: Double by Delegates.notNull()
        var ifToTxt: Boolean by Delegates.notNull()
        var doDoubleMatrixFun: DoubleMatrix.() -> DoubleMatrix by Delegates.notNull()
        var aDsDFun: IDsDFun by Delegates.notNull()

        val aNowTimeStr: String by lazy {
            getT4NowTimeString()
        }

        abstract val aGetEnvFun: () -> ZoneEnv

        open fun getAboutVarList(env: ZoneEnv) = env.clocks

        companion object {
            fun fRepeatT1ByLearningMakeStrategy(
                times: Int,
                action: (String) -> Unit,
                aBaseDirStr: String,
                shouldDrawChart: Boolean,
                debuggerList: DebuggerList = makeDebuggerList(),
            ) {
                val list: ArrayList<List<Double>> = arrayListOf()
                repeat(times) {
                    val aDirStr = aBaseDirStr + "/${it}"
                    action(aDirStr)
                    if (shouldDrawChart) {
                        val a4FileName = "${aBaseDirStr}/a4.txt"
                        T1SaveTuple.parseFileToScoreList(
                            a4FileName
                        ).toHistoryMinList().let {
                            list.add(it)
                        }
                    }
                }

                if (shouldDrawChart) {
                    drawLinesChart(
                        yDataList = list,
                        ifShowYDouble = false,
                        savedFileName = "${aBaseDirStr}/chart_min.png",
                    )
                }
            }
        }

        fun saveDoubleListToTextFile(doubleList: List<Double>, textFileString: String) {
            val file = textFileString.toCreatedFile()
            doubleList.forEach {
                file.appendText("${it}\n")
            }
            file.appendText("min=${doubleList.minOrNull()}\n")
        }

        fun t1SaveDoubleList(doubleList: List<Double>, textFileString: String, ifSaveAsPng: Boolean = false) {
            saveDoubleListToTextFile(doubleList, textFileString)
            if (ifSaveAsPng) {
                val yData = readDoubleListFromFile(textFileString)
                drawLineChart(
                    yData = yData,
                    ifShowYDouble = false,
                    savedFileName = textFileString.replaceLastOccurrence("chart_txt", "chart_png")
                        .replaceLastOccurrence(".txt", ".png"),
                )
            }
        }

        abstract fun byWeightSelectChoice(
            env: ZoneEnv,
            ren: SubT2SNodeUnit,
            locationName: String,
            locationEventVListLHM: LocationEventVListLHM,
            zs: Config,
            aSNode: SNode,
            nowTString: String,
            nowDoubleT: Double,
        )

        //对ren策略进行修改
        fun doWhenEnvRenLocation(
            env: ZoneEnv,
            ren: SubT2SNodeUnit,
            locationName: String,
            locationEventVListLHM: LocationEventVListLHM,
        ) {
            val aSNode = makeSNode()
            ren.relationLHM.add(null, locationName, aSNode)
            ren.funLHM.add(aSNode) { zs ->
                val nowDoubleT = zs.getTTime()
                require(nowDoubleT.isInt())
                val nowTString = nowDoubleT.toInt().toString()
                if (ren.relationLHM[aSNode, nowTString] == null) {
                    byWeightSelectChoice(
                        env,
                        ren,
                        locationName,
                        locationEventVListLHM,
                        zs,
                        aSNode,
                        nowTString,
                        nowDoubleT,
                    )
                }
                nowTString
            }
            (0..env.timeNeedEnd).forEach {
                val aZoneState = makeConfig(
                    arrayListOf(locationName),
                    arrayListOf(
                        makePointZoneConstraint(
                            listOf(makeCoeffVar(1, "t")),
                            it.toDouble(),
                        ),
                    ),
                )
                ren.funLHM[aSNode]!!(aZoneState)
            }
        }

        // ISNodeUnit是总策略
        // 在这里增加终止条件，给出最优策略
        fun <F : ISNodeUnit> fByLearningMakeStrategy(
            aBaseDirStr: String,
            aStrategyEx: IStrategyEx<F>,
            makeLearnedStrategyFun: (ZoneEnv, LocationEventVListLHM, IStrategyEx<F>) -> F,
            getUStrategyScoreFun: () -> Double = { Double.MAX_VALUE },
            getBestStrategyScoreFun: () -> Double = { Double.MAX_VALUE },
            debuggerList: DebuggerList = makeDebuggerList(),
        ): F {
            val uStrategyScore = getUStrategyScoreFun()
            val uStrategyScoreList = if (uStrategyScore != Double.MAX_VALUE) {
                listOf(uStrategyScore)
            } else {
                listOf()
            }

            val bestStrategyScore = getBestStrategyScoreFun()

            val argFile = "${aBaseDirStr}/arg.txt".toCreatedFile()
            val a4FileName = "${aBaseDirStr}/a4.txt"
            val a4File = a4FileName.toCreatedFile()

            val minStrategyFileName = "$aBaseDirStr/min/minStrategy.txt"
            val minStrategyFile = minStrategyFileName.toCreatedFile()

            val minListFileName = "$aBaseDirStr/min/chart_min.txt"

            argFile.appendText("T2ArgUnit=${this.toL2String()}\n")

            val iAU: InstanceArgUnit = makeInstanceArgUnit()

            val env: ZoneEnv = aGetEnvFun()
            val aStrategyCalLHMList: ArrayList<EDoubleLHM<F>> = ArrayList()

            var aStrategyMeanCostLHM: EDoubleLHM<F> = makeEDoubleLHM()
            var nowStrategy: F = aStrategyEx.makeFixedStrategy()

            plnCal(getEnvStrategyCal(env, nowStrategy, debuggerList))
            repeat(maxIterations) iteration@{
//                if (it > 100 && iAU.lastMinCost <= bestStrategyScore) {
//                    return@iteration
//                }
                if (iAU.countOfReset >= maxResets) {
                    return@iteration
                }
                if (iAU.ifReset) {
                    println("${it},重置")
                    iAU.ifReset = false
                    iAU.countOfReset += 1
                    if (iAU.countOfReset >= maxResets) {
                        return@iteration
                    }
                    iAU.countOfNoBetter = 0
                    iAU.indexAfterReset = 0
                    iAU.lastMinCost = Double.MAX_VALUE
                    iAU.aRunResultList.retainPercentage(1 - clearHeapP)
                    aStrategyMeanCostLHM = makeEDoubleLHM()
                    aStrategyCalLHMList.add(aStrategyMeanCostLHM)
                    nowStrategy = aStrategyEx.makeFixedStrategy()
                }
                val lineForA4FileSB = StringBuilder()
                lineForA4FileSB.run {
                    append("$it".padEnd(8))
                    append("${iAU.countOfReset}".padEnd(8))
                    append("${iAU.indexAfterReset}".padEnd(8))
                }

                iAU.indexAfterReset++

                //模拟运行，用于选取cost小的运行实例
                val rrs = env.repeatRandomRun(
                    aSNU = nowStrategy,
                    repeatTimes = maxRuns_repeatNum,
                    debuggerList = debuggerList,
                )

                rrs.sortedBy {
                    getTotalScore(it.endData.toEDoubleLHM())
                }.take(maxGood_repeatNum).forEach {
                    iAU.aRunResultList.add(it)
                }
                iAU.aRunResultList = iAU.aRunResultList.sortedBy {
                    getTotalScore(it.endData.toEDoubleLHM())
                }.take(
                    maxBest_heapSize,
                ).toArrayList()

                val locationEventVListLHM: LocationEventVListLHM = iAU.aRunResultList.toLocationEventVListLHM(
                    getAboutVarList(env),
                    doDoubleMatrixFun,
                )

                val oldStrategy = nowStrategy
                nowStrategy = makeLearnedStrategyFun(
                    env,
                    locationEventVListLHM,
                    aStrategyEx,
                )

                val cal: A2LHM<String, Double> = getEnvStrategyCal(env, nowStrategy, debuggerList)
                val mean: Double = getTotalScore(cal)
                lineForA4FileSB.run {
                    append("$mean".padEnd(25))
                }
                lineForA4FileSB.toString().let {
                    a4File.appendText("${it}\n")
                    println(it)
                }

                aStrategyMeanCostLHM[nowStrategy] = mean

                if (mean < iAU.lastMinCost) {
                    iAU.countOfNoBetter = 0
                    iAU.lastMinCost = mean
                } else {
                    nowStrategy = oldStrategy
                    iAU.countOfNoBetter += 1
                    if (iAU.countOfNoBetter >= maxNoBetter) {
                        iAU.ifReset = true
                    }
                }
            }

            //txt,png
            aStrategyCalLHMList.withIndex().forEach { (index: Int, lhm) ->
                require(lhm.size > 0)
                val aCalList = lhm.map {
                    it.value
                }

                t1SaveDoubleList(
                    doubleList = aCalList,
                    textFileString = "${aBaseDirStr}/chart_txt/chart${index}.txt",
                )
            }
            //
            T1SaveTuple.parseFileToScoreList(
                a4FileName
            ).let {
                drawLineAndHistoryMinLineChart(
                    yData = uStrategyScoreList + it,
                    savedFileName = "${aBaseDirStr}/a4.png",
                )
            }
            //
            // 获取每轮迭代的最低值
            val aMinaCalList = ArrayList<Double>()
            aStrategyCalLHMList.forEach {
                aMinaCalList.add(
                    it.values.minOrNull()!!,
                )
            }
            // 保存每轮迭代的最低值
            saveDoubleListToTextFile(
                doubleList = aMinaCalList,
                textFileString = minListFileName,
            )
            // 绘图
            minListFileName.fileNameToDoubleList().let {
                drawLineAndHistoryMinLineChart(
                    yData = uStrategyScoreList + it,
                    savedFileName = "$aBaseDirStr/min/minList.png",
                )
            }
            //
            // 找到最优策略
            val aMinStrategy = getMinStrategy(
                aMinaCalList,
                aStrategyCalLHMList,
                debuggerList,
            )
            // 最优策略的评价值
            getEnvStrategyCal(env, aMinStrategy, debuggerList).let { cal ->
                val calString = getCalString(cal)
                println(calString)
                minStrategyFile.appendText(aISNodeStringFun(aMinStrategy))
            }
            return aMinStrategy
        }

        fun <F : ISNodeUnit> getMinStrategy(
            aMinaCalList: List<Double>,
            aStrategyCalLHMList: ArrayList<EDoubleLHM<F>>,
            debuggerList: DebuggerList,
        ): F {
            require(aMinaCalList.isNotEmpty())
            println("last_test")
            val getMinValueKey = aMinaCalList.getMinValueKey()!!
            println("getMinValueKey=${getMinValueKey}")
            return aStrategyCalLHMList[getMinValueKey].getMinKey()!!
        }

        abstract fun getEnvStrategyCal(
            aEnv: ZoneEnv,
            aStrategy: ISNodeUnit,
            debuggerList: DebuggerList,
        ): A2LHM<String, Double>

        open fun aISNodeStringFun(aStrategy: ISNodeUnit) = aStrategy.toStr0()

        abstract val aEnvLabel: String

        val aBaseDirStr: String by lazy {
            buildString {
                append(FileRes.out_chart_file)
                append("/LearningTest_${aEnvLabel}")
                append("/t_${aNowTimeStr}_${getDFun.name}")
            }
        }

        override fun toStr(tabNum: Int): String {
            val sb = StringBuilder()
            val tsu = makeTabStringUnit(tabNum)
            sb.append("${tsu.tabNumStr0}T2ArgUnit(\n")
            maxRuns_repeatNum.toSBStr(sb, tsu, "maxRuns_repeatNum", 1)
            maxGood_repeatNum.toSBStr(sb, tsu, "maxGood_repeatNum", 1)
            maxBest_heapSize.toSBStr(sb, tsu, "maxBest_heapSize", 1)
            maxNoBetter.toSBStr(sb, tsu, "maxNoBetter", 1)
            maxIterations.toSBStr(sb, tsu, "maxIterations", 1)
            maxResets.toSBStr(sb, tsu, "maxResets", 1)
            getDFun.toSBStr(sb, tsu, "getDFun", 1)
            clearHeapP.toSBStr(sb, tsu, "clearHeapP", 1)
            ifToTxt.toSBStr(sb, tsu, "ifToTxt", 1)
            doDoubleMatrixFun.toSBStr(sb, tsu, "doDoubleMatrixFun", 1)
            aDsDFun.toSBStr(sb, tsu, "aDsDFun", 1)
            sb.append("${tsu.tabNumStr0})\n")
            return sb.toString().removeEmptyLine()
        }

        override fun toL2String(): String {
            val sb = StringBuilder()
            sb.append("T2ArgUnit(\n")
            sb.append("\tmaxRuns_repeatNum=$maxRuns_repeatNum\n")
            sb.append("\tmaxGood_repeatNum=$maxGood_repeatNum\n")
            sb.append("\tmaxBest_heapSize=$maxBest_heapSize\n")
            sb.append("\tmaxNoBetter=$maxNoBetter\n")
            sb.append("\tmaxIterations=$maxIterations\n")
            sb.append("\tmaxResets=$maxResets\n")
            sb.append("\tgetDFun=${getDFun}\n")
            sb.append("\tclearHeapP=$clearHeapP\n")
            sb.append("\tifToTxt=$ifToTxt\n")
            sb.append("\tdoDoubleMatrixFun=${doDoubleMatrixFun}\n")
            sb.append("\taDsDFun=${aDsDFun}\n")
            sb.append(")")
            return sb.toString()
        }
    }
}